/* $Id: LibraryLoader.java 36 2012-01-25 14:07:21Z chr.grote@gmail.com $
 * Created on 02.10.2004
 */
package net.sourceforge.ftgl.util.loader;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.util.HashSet;
import java.util.logging.Level;

import net.sourceforge.ftgl.demos.util.Logger;
import net.sourceforge.ftgl.demos.util.Tools;

/**
 * TODO javadoc
 * 
 * @author funsheep
 */
public class LibraryLoader
{
	private static final Logger			LOGGER		= Logger.getLogger();
	private static final ClassLoader	CLASSLOADER	= LibraryLoader.class.getClassLoader();

	private final static String			PREFIX		= "wubivipu_";

	private final static HashSet<String>		cache		= new HashSet<String>();

	private final OSInfo				osinfo;

	private static synchronized void load0(String libname, String path, boolean windows)
	{
		if (cache.contains(path))
			return;
		final URL u = CLASSLOADER.getResource(path);
		if (u == null)
			throw new RuntimeException("file " + path + " not found");

		assert LOGGER.log(Level.FINE, "library {0} found at {1}", new Object[] { libname, u });

		File p;

		if (u.getProtocol().equalsIgnoreCase("file"))
		{
			p = new File(u.getPath());;
			LOGGER.log(Level.CONFIG, "using file {0}", p);
		} else
			try
			{
				if (windows)
					p = extractLibraryWindows(libname, u);
				else
					p = extractLibraryOther(libname, u);
			}
			catch (final IOException e)
			{
				throw new RuntimeException("could not extract library", e);
			}

		System.load(p.getAbsolutePath());

		p.delete();

		cache.add(path);
	}

	public LibraryLoader()
	{
		this.osinfo = new OSInfo();
	}

	public void load(String libname)
	{
		libname = System.mapLibraryName(libname);

		final String osname = this.osinfo.getOSName();
		final String osarch = this.osinfo.getArchName();
		final String path = "natives/" + osname + "/" + osarch + "/" + libname;

		LOGGER.log(Level.CONFIG, "loading library {0}", path);

		load0(libname, path, osname.equals(OSInfo.OS_WIN));
	}

	public OSInfo getOSInfo()
	{
		return this.osinfo;
	}

	private static File extractLibraryWindows(String libname, URL u) throws IOException
	{
		final String tmpdir = System.getProperty("java.io.tmpdir", "");
		if (tmpdir.length() < 1)
			throw new RuntimeException("no temp dir");

		assert LOGGER.log(Level.FINE, "using tempdir {0}", tmpdir);

		final String prefix = tmpdir + File.separator + PREFIX;

		int c = 0;
		while (true)
		{
			c++;
			final File f = new File(prefix + c + libname);

			f.delete();

			if (f.createNewFile())
			{
				LOGGER.log(Level.CONFIG, "created file {0}", f);

				InputStream i = null;
				OutputStream o = null;

				try
				{
					i = u.openStream();
					o = new FileOutputStream(f);

					Tools.transfer(i, o);

					return f;
				}
				finally
				{
					Tools.close(i);
					Tools.close(o);
				}
			}

			if (f.isFile())
			{
				assert LOGGER.log(Level.FINER, "testing " + f);

				InputStream i1 = null;
				InputStream i2 = null;
				try
				{
					i1 = u.openStream();
					i2 = new FileInputStream(f);

					if (Tools.compare(i1, i2))
					{
						LOGGER.log(Level.CONFIG, "using file {0}", f);

						return f;
					}
				}
				catch (final IOException e)
				{
					LOGGER.log(Level.WARNING, "i/o-error", e);
				}
				finally
				{
					Tools.close(i1);
					Tools.close(i2);
				}
			}
		}
	}

	private static File extractLibraryOther(String libname, URL u) throws IOException
	{
		final File f = File.createTempFile(PREFIX, libname);

		LOGGER.log(Level.CONFIG, "created file {0}", f);

		InputStream i = null;
		OutputStream o = null;
		try
		{
			i = u.openStream();
			o = new FileOutputStream(f);
			Tools.transfer(i, o);
		}
		finally
		{
			Tools.close(i);
			Tools.close(o);
		}

		return f;
	}

}